package com.imooc.bilibili.util;

import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.model.*;
import com.imooc.bilibili.exception.ConditionException;
import com.mysql.cj.util.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.util.ArrayList;
import java.util.List;

/**
 * @author xiexu
 * @create 2022-08-11 22:04
 */
@Component
public class OSSUtil {

    @Value("${oss.endpoint}")
    private String endpoint;

    @Value("${oss.accessKeyId}")
    private String accessKeyId;

    @Value("${oss.accessKeySecret}")
    private String accessKeySecret;

    @Value("${oss.bucketName}")
    private String bucketName;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    private static final String UPLOADED_NO_KEY = "uploaded-no-key:";

    private static final String UPLOADED_ID_KEY = "uploaded-id-key:";


    /**
     * 获取文件的类型
     *
     * @param file
     * @return
     */
    public String getFileType(MultipartFile file) {
        if (file == null) {
            throw new ConditionException("非法文件！");
        }
        // 获取文件名称
        String fileName = file.getOriginalFilename();
        // 获取文件名称最后一个"."的位置，这样子可以根据"."的位置截取出文件的类型
        int index = fileName.lastIndexOf(".");
        // 获取文件的类型
        String fileType = fileName.substring(index + 1);
        return fileType;
    }

    /**
     * 创建 OSSClient
     *
     * @return
     */
    public OSS createOSSClient() {
        // 创建OSSClient实例
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
        return ossClient;
    }

    /**
     * 创建存储空间
     *
     * @param ossClient
     */
    public void createBucket(OSS ossClient) {
        try {
            // 创建存储空间。
            ossClient.createBucket(bucketName);
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, " + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            System.out.println("Caught an ClientException, which means the client encountered " + "a serious internal problem while trying to communicate with OSS, " + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }

    /**
     * 通过文件分片上传一个文件
     *
     * @param file
     * @param fileMd5
     * @return
     */
    public String uploadFileBySlices(MultipartFile file, String fileMd5) {
        if (file == null) {
            throw new ConditionException("参数异常！");
        }
        // 当前已经上传成功的分片号
        String uploadedNoKey = UPLOADED_NO_KEY + fileMd5;
        String uploadIdKey = UPLOADED_ID_KEY + fileMd5;

        String objectName = file.getOriginalFilename();
        // 创建OSSClient实例
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
        try {
            // 创建InitiateMultipartUploadRequest对象
            InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, objectName);

            // 初始化分片
            InitiateMultipartUploadResult upresult = ossClient.initiateMultipartUpload(request);
            // 返回uploadId，它是分片上传事件的唯一标识。您可以根据该uploadId发起相关的操作，例如取消分片上传、查询分片上传等
            String uploadId = null;
            String strs = redisTemplate.opsForValue().get(uploadIdKey);
            if (!StringUtils.isNullOrEmpty(strs)) {
                uploadId = strs;
            } else {
                uploadId = upresult.getUploadId();
                redisTemplate.opsForValue().set(uploadIdKey, uploadId);
            }
            // String uploadId = upresult.getUploadId();

            // partETags是PartETag的集合。PartETag由分片的ETag和分片号组成
            List<PartETag> partETags = new ArrayList<PartETag>();
            // 每个分片的大小，用于计算文件有多少个分片。单位为字节
            final long partSize = 2 * 1024 * 1024L;   //2 MB
            File sampleFile = this.MultipartFileToFile(file);
            long fileLength = sampleFile.length();
            int partCount = (int) (fileLength / partSize);
            if (fileLength % partSize != 0) {
                partCount++;
            }
            System.out.println("partCount = " + partCount);

            String str = redisTemplate.opsForValue().get(uploadedNoKey);
            int i = 0;
            if (!StringUtils.isNullOrEmpty(str)) {
                i = Integer.parseInt(str);
            }

            // 遍历分片上传
            for (; i < partCount; i++) {

                long startPos = i * partSize;
                long curPartSize = (i + 1 == partCount) ? (fileLength - startPos) : partSize;
                InputStream instream = new FileInputStream(sampleFile);
                // 跳过已经上传的分片
                instream.skip(startPos);
                UploadPartRequest uploadPartRequest = new UploadPartRequest();
                uploadPartRequest.setBucketName(bucketName);
                uploadPartRequest.setKey(objectName);
                uploadPartRequest.setUploadId(uploadId);
                uploadPartRequest.setInputStream(instream);
                // 设置分片大小。除了最后一个分片没有大小限制，其他的分片最小为100 KB
                uploadPartRequest.setPartSize(curPartSize);

                // --------
                // 设置分片号。每一个上传的分片都有一个分片号，取值范围是1~10000，如果超出此范围，OSS将返回InvalidArgument错误码。
                uploadPartRequest.setPartNumber(i + 1);
                redisTemplate.opsForValue().set(uploadedNoKey, String.valueOf(i));
                // ---------


                // 每个分片不需要按顺序上传，甚至可以在不同客户端上传，OSS会按照分片号排序组成完整的文件。
                UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest);
                // 每次上传分片之后，OSS的返回结果包含PartETag。PartETag将被保存在partETags中。
                partETags.add(uploadPartResult.getPartETag());
            }

            // 创建CompleteMultipartUploadRequest对象。
            // 在执行完成分片上传操作时，需要提供所有有效的partETags。OSS收到提交的partETags后，会逐一验证每个分片的有效性。当所有的数据分片验证通过后，OSS将把这些分片组合成一个完整的文件。
            CompleteMultipartUploadRequest completeMultipartUploadRequest = new CompleteMultipartUploadRequest(bucketName, objectName, uploadId, partETags);

            // 完成分片上传。
            CompleteMultipartUploadResult completeMultipartUploadResult = ossClient.completeMultipartUpload(completeMultipartUploadRequest);

            // 如果已经上传的分片数 等于 总的分片数，则将redis相关的key清除
            if (i == partCount) {
                redisTemplate.delete(uploadedNoKey);
            }

            return completeMultipartUploadResult.getLocation();

        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, " + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            System.out.println("Caught an ClientException, which means the client encountered " + "a serious internal problem while trying to communicate with OSS, " + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
        return null;
    }

    /**
     * 将MultipartFile转换成File类型
     *
     * @param multipartFile
     * @return
     */
    public File MultipartFileToFile(MultipartFile multipartFile) throws Exception {
        // 获取文件名称
        String originalFilename = multipartFile.getOriginalFilename();
        // 数组包含文件名称和文件类型，例如：mianshi.pdf
        String[] fileName = originalFilename.split("\\.");
        File file = File.createTempFile(fileName[0], "." + fileName[1]);
        multipartFile.transferTo(file);
        return file;
    }

}
